會開始學習使用 react-table 是有故事的,因為工作上有個需求是要讓表格需要排序、分頁功能,如果只是這樣的話,我內心想的是 MUI 的 DataGrid 也許能解決這需求,只要直接輸入資料就能擁有功能,結果後來發現不行,但...就沒有給表格格子操作的空間,另外我的樣式都是自己寫的。MUI 自帶的樣式我還得要一個要把它壓過去,感覺不太舒服...疼痛許久,轉向了 headless (不帶樣式的) 的套件,最後留下了 react-table。
其實我剛開始使用 react-table 時碰到各種障礙 :
花不少時間做出功能,利用這次鐵人賽的機會,跟大家分享學習 react-table 的同時,也幫自己複習知識。
TanStack Table v8 是一款支援多個框架的套件,react-table 是它支援 react 的版本。
react-table 利用對表格資料的掌控,提供篩選、排序、分頁...等更多的功能,是強大的套件,提供的每個功能都可以個別裝上去,解決需要高度客製化的問題。
而且它有詳盡的官方文件,這也是一大優點。
閱讀這一頁來了解 react-table 的 core。
提醒一下大家,要安裝的是 v8 版本,不要裝錯了喔。
使用 react-table 需要更加了解網頁中的表格元素。
閱讀 MDN 的 HTML 表格的基礎,能夠有效的補足這方面的知識。
基本用法就是在 <table>
標籤中,用 <tr>
與 <tb>
組成表格,附上官方的範例 :
<table>
<tr>
<td> </td>
<td>Knocky</td>
<td>Flor</td>
<td>Ella</td>
<td>Juan</td>
</tr>
<tr>
<td>Breed</td>
<td>Jack Russell</td>
<td>Poodle</td>
<td>Streetdog</td>
<td>Cocker Spaniel</td>
</tr>
<tr>
<td>Age</td>
<td>16</td>
<td>9</td>
<td>10</td>
<td>5</td>
</tr>
<tr>
<td>Owner</td>
<td>Mother-in-law</td>
<td>Me</td>
<td>Me</td>
<td>Sister-in-law</td>
</tr>
<tr>
<td>Eating Habits</td>
<td>Eats everyone's leftovers</td>
<td>Nibbles at food</td>
<td>Hearty eater</td>
<td>Will eat till he explodes</td>
</tr>
</table>
除此之外,表格中另外有提供 <th>
來標記表頭 :
<table>
<tr>
<th>Data 1</th> // 它是表頭
<th>Data 2</th> // 它是表頭
</tr>
<tr>
<td>Calcutta</td>
<td>Orange</td>
</tr>
<tr>
<td>Robots</td>
<td>Jazz</td>
</tr>
</table>
我們還能夠用語意化標籤為表格分類出表頭、表身還有表尾,分別用 <thead>
、<body>
、<tfoot>
來包裹元素。
<table>
<thead>
<tr>
<th>Items</th>
<th scope="col">Expenditure</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Donuts</th>
<td>3,000</td>
</tr>
<tr>
<th scope="row">Stationery</th>
<td>18,000</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Totals</th>
<td>21,000</td>
</tr>
</tfoot>
</table>
進入安裝頁的時候會發現很多選擇,這邊我們安裝 react 版本 :
$ npm install @tanstack/react-table
使用 react-table 的時候需要事先定義好 Columns (也就是表頭的部分),第二項需要定義的內容就是要做成表格的資料內容,最少是一定要放入 data 與 column,以下是官方的範例 :
import {
createColumnHelper, // 幫忙製作表格列的工具
flexRender, // 其實就是 flex box
getCoreRowModel, // 取得行的資料來渲染新表格
useReactTable, // 使用此 Hook 來掌握表格
} from "@tanstack/react-table";
const table = useReactTable({
data, // 輸入表格的資料
columns, // 輸入定義好的表頭
getCoreRowModel: getCoreRowModel(),
});
資料需要用 type 先定義好,在設進去 columns :
type Person = {
firstName: string
lastName: string
age: number
visits: number
status: string
progress: number
}
columns :
const columnHelper = createColumnHelper<Person>()
const columns = [
columnHelper.accessor('firstName', {
cell: info => info.getValue(),
footer: info => info.column.id,
}),
columnHelper.accessor(row => row.lastName, {
id: 'lastName',
cell: info => <i>{info.getValue()}</i>,
header: () => <span>Last Name</span>,
footer: info => info.column.id,
}),
columnHelper.accessor('age', {
header: () => 'Age',
cell: info => info.renderValue(),
footer: info => info.column.id,
}),
columnHelper.accessor('visits', {
header: () => <span>Visits</span>,
footer: info => info.column.id,
}),
columnHelper.accessor('status', {
header: 'Status',
footer: info => info.column.id,
}),
columnHelper.accessor('progress', {
header: 'Profile Progress',
footer: info => info.column.id,
}),
]
data :
const defaultData: Person[] = [
{
firstName: 'tanner',
lastName: 'linsley',
age: 24,
visits: 100,
status: 'In Relationship',
progress: 50,
},
{
firstName: 'tandy',
lastName: 'miller',
age: 40,
visits: 40,
status: 'Single',
progress: 80,
},
{
firstName: 'joe',
lastName: 'dirte',
age: 45,
visits: 20,
status: 'Complicated',
progress: 10,
},
]
<colgroup>
會認識這個元素,是因為我用 react-table 把表格蓋好之後,收到需求方想要在表格的中心加上一條分隔線,當下我的想法是 :等下,我要在動態生成的表格中寫 css 來加一條線,感覺怪怪的啊,資料是動態的,這意味著會變動的資料我並不能知道選擇器要寫在哪裡(這個狀態是沒辦法用 tailwind 的)...就在我想著要寫一百個選擇器來加一條線的時候,我找到了 <colgroup>
,簡直就是救星!
關於<colgroup>
的事情在 MDN 中有講到,可以在表格存在但沒辦法個別對一個 <th>
去做樣式改變的時候(tailwind 跟傳統 css 選擇器很難做的調整),做到對表格一整行的樣式修改。
附上我實際撰寫的程式碼,如下 :
<div className="mb-8">
<Table>
<colgroup>
<col span={8}></col>
<col className="border-l-[1px] border-[#734A42]"></col> //加上這一行就能在第八行到第10行之間的 <th> 加上 className : border-l-[1px] border-[#734A42]
<col span={10}></col>
</colgroup>
<Thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
colSpan={header.colSpan}
{...{
onClick: header.column.getToggleSortingHandler(),
}}
>
{header.isPlaceholder ? null : (
<div>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{{
asc: "?",
desc: "?",
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
</th>
);
})}
</tr>
))}
</Thead>
<tbody className="bg-white">
{table.getRowModel().rows.map((row, i) => (
<tr className="border-b border-gray-200" key={row.id}>
{row.getVisibleCells().map((cell, i) => (
<td
className={`${
isOverZero(cell.getContext().getValue())
? "text-red cursor-pointer bg-gray-50"
: ""
}`}
data-rowIndex={i}
key={cell.id}
onClick={async (e) => {
const rowIndex = e.currentTarget.dataset.rowindex as string;
const singerCode = await getSignerCode(e, signers);
const unitCode = (await getUnitCode(e, units)) as string;
const value = await cell.getContext().getValue();
if (!value) {
return;
} else {
dispatch(doClick());
const singerName = await getSingerName(e);
const JBtype = getCurrentJBType(rowIndex) as string[];
dispatch(setSingerName(singerName));
dispatch(setSignType(JBtype[1]));
await dispatch(
fetchQuerySignerDetail({
DateType: dateType,
DateStart: dateStart,
DateEnd: dateEnd,
SignerCode: singerCode,
UnitGCode: unitCode,
JBType: JBtype[0],
})
);
dispatch(openModal());
}
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
<tfoot className="bg-[#734A42] text-[#FFE600]">
<tr>
<td colSpan={2}>總計</td>
<td>{footerData.UMJB1}</td>
<td>{footerData.UMJB100}</td>
<td>{footerData.UMJB150}</td>
<td>{footerData.UJB1}</td>
<td>{footerData.UJB100}</td>
<td>{footerData.UJB150}</td>
<td>{footerData.MJB1}</td>
<td>{footerData.MJB100}</td>
<td>{footerData.MJB150}</td>
<td>{footerData.JB1}</td>
<td>{footerData.JB100}</td>
<td>{footerData.JB150}</td>
</tr>
</tfoot>
</Table>
</div>
跟這個專案演練無關,這是當初研究時做的練習,是用 js 而不是 ts,送給大家參考。
學習套件的同時補強基本知識,也是一種學習跟複習的方法,對我而言是最不無聊的學習方式,滿好玩的。
例如 :學 react-hook-form 的同時穩固對網頁表單的知識,在學習 react-table 的同時更熟的掌握對網頁表格的理解,對我來說一邊實作一邊補足基本知識,當然還有 typescript 的熟練度也因此提升了...
<thead>
元素
<tbody>
元素
<tfoot>
元素
<colgroup>
元素